package com.hzit.order.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson2.JSON;

import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayConfig;
import com.alipay.api.domain.AlipayAssetVoucherTemplateInfoQuerybudgetModel;
import com.alipay.api.internal.util.AlipaySignature;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.hzit.order.dto.ItemDTO;
import com.hzit.order.dto.OrderDTO;
import com.hzit.order.entity.Order;
import com.hzit.order.entity.OrderItem;
import com.hzit.order.entity.PayLog;
import com.hzit.order.interceptor.LoginInterceptor;
import com.hzit.order.mapper.OrderMapper;
import com.hzit.order.mapper.PayLogMapper;
import com.hzit.order.service.IOrderItemService;
import com.hzit.order.service.IOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hzit.order.service.IPayLogService;
import com.hzit.order.vo.OrderVO;
import com.ruoyi.common.core.config.AlipayTemplate;
import com.ruoyi.common.core.constant.HttpStatus;
import com.ruoyi.common.core.constant.HzitConstants;
import com.ruoyi.common.core.exception.OrderRepeableSubmitException;
import com.ruoyi.common.core.utils.IdWorker;
import com.ruoyi.common.core.utils.uuid.IdUtils;
import com.ruoyi.system.api.domain.PayAsyncVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author wf
 * @since 2024-04-10
 */
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {
	@Autowired
	private StringRedisTemplate redisTemplate;
	@Autowired
	private ThreadPoolExecutor executor;
	@Autowired
	private IdWorker idWorker;
	@Autowired
	private IOrderItemService orderItemService;
	@Autowired
	private IPayLogService payLogService;
	@Autowired
	private AlipayTemplate alipayTemplate;
	//1. 根据ids从redis中查询出商品列表
	@Override
	public List<ItemDTO> findItemListByIds(String ids) {
		List<ItemDTO> itemDTOS = new ArrayList<>();
		String[] split = ids.split(",");
		for (String id : split) {
			String str = (String) redisTemplate.opsForHash().get(HzitConstants.CART_PREFIX + getLogin(), id);
			ItemDTO itemDTO = JSON.parseObject(str, ItemDTO.class);
			itemDTOS.add(itemDTO);
		}
		return itemDTOS;
	}

	@Override
	public String createToken() {
		//1. 生成一个随机的token值
		String token = UUID.randomUUID().toString().replace("-", "");
		//2. 保存到redis中
		redisTemplate.opsForValue().set(HzitConstants.ORDER_TOKEN + getLogin(),token);
		return token;
	}
	//2. 保存订单
	@Override
	@Transactional
	public void saveOrder(OrderDTO dto) throws ExecutionException, InterruptedException {
		//2.1 确保订单的幂等性
		//2.1.1 得到表单提交过来的token值
		String token = dto.getToken();
		//2.2 为了保证提交订单的幂等性，我们使用lua脚本+redis方式完成幂等性处理
		String scriptStr = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
		RedisScript<Long> script = new DefaultRedisScript<>(scriptStr,Long.class);
		ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		// 参数1：要执行的lua脚本
		// 参数2：keys，这里可以使用redis中的token所对应的key
		// 参数3：ARGV，这里可以使用表单提交过来的token值
		Long rs = redisTemplate.execute(script, Arrays.asList(HzitConstants.ORDER_TOKEN + getLogin()), token);
		//2.3 如果结果为0L，代表执行
		if(rs == 0L){
			throw new OrderRepeableSubmitException(HttpStatus.BAD_REQUEST,HzitConstants.ORDER_REPEAT_SUBMIT_MSG);
		}
		//2.4 创建订单及订单项列表
		List<Order> orders = new ArrayList<>();
		List<OrderItem> items = new ArrayList<>();
		//2.5 为订单及订单项列表赋值，得到返回的所有订单的总金额
		BigDecimal totalMoney = createOrderAndOrderItems(orders,items,dto);

		//3. 开始添加订单及订单项列表
		//3.1 保存订单
		CompletableFuture<Void> saveOrderFuture = CompletableFuture.runAsync(() -> {
				RequestContextHolder.setRequestAttributes(requestAttributes);
				// 批量保存订单
				this.saveBatch(orders);
		}, executor);
		//3.2 保存订单项
		CompletableFuture<Void> saveOrderItemFuture = CompletableFuture.runAsync(() -> {
			RequestContextHolder.setRequestAttributes(requestAttributes);
			   // 批量保存
			  orderItemService.saveBatch(items);
		}, executor);

		//3.4 扣减库存，避免库存超卖（使用锁机制），此时要锁住库存，（只是锁定我们购买的订单id对应的订单项列表）
		// 思路：
		// 1. 我们扣减库存时，我们把要购买的商品id以消息中间件发送给库存微服务，由它们自动锁库存
		// 2. 我们可以在库存这边设置锁时间，如1m，如果时间到了就自动解锁库存
		// 3. 中间件发送消息时如何保证消息不丢失？（rabbitmq或rocketmq如何保证消息不丢失）
		// 参见：https://mp.weixin.qq.com/s?__biz=MzI4Njc5NjM1NQ==&mid=2247493757&idx=1&sn=914ba23493199e1e4fd34be063f0215f&chksm=ebd5d751dca25e4752c921f4250c98fab02c8741c107e9b97bc257fce8c75dfdfb4c9eab307e&scene=21#wechat_redirect

		//3.3 保存支付日志

		CompletableFuture<Void> savePayLogFuture = CompletableFuture.runAsync(() -> {
			RequestContextHolder.setRequestAttributes(requestAttributes);
			String login = requestAttributes.getRequest().getSession().getAttribute("login") + "";

			//3.3.1 创建支付日志
			PayLog payLog = createPayLog(dto, orders, totalMoney,login);
			//3.3.2 保存到db中
			payLogService.save(payLog);
			//3.3.3 保存到redis中
			redisTemplate.opsForValue().set(HzitConstants.PAYLOG_PREFIX + login,JSON.toJSONString(payLog),30, TimeUnit.DAYS);
		}, executor);

		//3.4 等待所有任务完成后，再进行后面的业务
		CompletableFuture<Void> future = CompletableFuture.allOf(saveOrderFuture, saveOrderItemFuture,savePayLogFuture);
		future.join();
	}
	// 从redis中获取支付日志
	@Override
	public PayLog getPayLogFromRedis() {
		String s = redisTemplate.opsForValue().get(HzitConstants.PAYLOG_PREFIX + getLogin());
		return JSON.parseObject(s,PayLog.class);
	}

	// 根据登录用户名查询订单及订单项列表
	@Override
	public List<OrderVO> findOrderListByLoginName() {
		//1.1 获取登录用户
		String login = getLogin();
		List<Order> orders = this.list(new QueryWrapper<Order>().eq("user_id", login));
		List<OrderVO> collect = orders.stream().map(m -> {
			//复制order到orderVo对象中
			OrderVO vo = new OrderVO();
			BeanUtil.copyProperties(m, vo, true);
			// 根据orderId查询此订单下的订单项列表
			List<OrderItem> orderItems = orderItemService.list(new QueryWrapper<OrderItem>().eq("order_id", m.getOrderId()));
			vo.setOrderItems(orderItems);
			return vo;
		}).collect(Collectors.toList());

		return collect;
	}

	// 支付宝异步通知
	@Override
	public String asyncNotify(HttpServletRequest request, PayAsyncVo vo) throws AlipayApiException {
		//1. 定义要验签时用到的map（存放验签数据）
		Map<String,String> params = new HashMap<String,String>();
		//2. 得到支付宝传入的数据
		Map<String,String[]> requestParams = request.getParameterMap();   // {"username":{"张三"},"password":"123","hobby":{"game","swim}}
		//3. 将Map<String,String[]>转换为Map<String,String>格式
		for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {
			String name = (String) iter.next();
			String[] values = (String[]) requestParams.get(name);
			String valueStr = "";
			// {"aa","bb","cc"}---> "aa,bb,cc"
			for (int i = 0; i < values.length; i++) {
				valueStr = (i == values.length - 1) ? valueStr + values[i]
						: valueStr + values[i] + ",";
			}
			//乱码解决，这段代码在出现乱码时使用
			params.put(name, valueStr);
		}
		//4. 进行验签处理
		boolean signVerified = AlipaySignature.rsaCheckV1(params, alipayTemplate.getAlipay_public_key(),
				alipayTemplate.getCharset(),
				alipayTemplate.getSign_type()); //调用SDK验证签名
		//5. 如果验签成功
		if(signVerified){
			//5.1 如果交易状诚完成或成功，就执行我们自己的业务
			if (vo.getTrade_status().equals("TRADE_SUCCESS") || vo.getTrade_status().equals("TRADE_FINISHED")) {
				//5.1.1 修改支付日志
				//① 得到订单号
				String out_trade_no = vo.getOut_trade_no();
				// String s = redisTemplate.opsForValue().get(HzitConstants.PAYLOG_PREFIX + getLogin());
				// PayLog payLog = JSON.parseObject(s, PayLog.class);
				PayLog payLog = payLogService.getById(out_trade_no);
				payLog.setPayTime(LocalDateTime.now());     // 修改支付时间
				payLog.setTransactionId(vo.getTrade_no());  // 修改支付流水号
				payLog.setTradeState("1");                  // 1: 代表己支付 0： 未支付

				//② 修改到数据库
				payLogService.updateById(payLog);
				//todolist... 从redis中删除日志

				//5.1.2 修改订单状态
				String orderList = payLog.getOrderList();
				String[] ids = orderList.split(",");
				for (String id : ids) {
					Order order = this.getById(id);
					order.setPaymentTime(LocalDateTime.now());      // 修改支付时间
					order.setStatus("2");                           // 1：未支付 2：己支付
					this.updateById(order);
				}
			}
			return "success";
		}

		return "fail";
	}

	private PayLog createPayLog(OrderDTO dto, List<Order> orders, BigDecimal totalMoney,String login) {
		//3.3.1 构造一个支付日志对象
		PayLog payLog = new PayLog();
		payLog.setTradeState("0");      // 0: 未支付 1：己支付
		payLog.setUserId(login);   // 用户id
		List<Long> ids = orders.stream().map(m -> m.getOrderId()).collect(Collectors.toList());
		payLog.setOrderList(ids.toString().replace("[","").replace("]",""));    // 设置订单号
		payLog.setCreateTime(LocalDateTime.now());
		payLog.setTotalFee(totalMoney.longValue());     // 订单总金额
		payLog.setPayType(dto.getPaymentType());        // 支付类型
		long outTradeNo = idWorker.nextId();            // 生成订单号
		payLog.setOutTradeNo(outTradeNo + "");
		return payLog;
	}

	// 构造订单及订单项集合，并返回订单总金额
	private BigDecimal createOrderAndOrderItems(List<Order> orders, List<OrderItem> items, OrderDTO dto) {
		BigDecimal[] totalMoney = {new BigDecimal(0.0)};        // 代表所有订单的总金额
		//1. 从dto中得到商品id
		String str = dto.getIds();
		String[] ids = str.split(",");
		List<ItemDTO> dtos = new ArrayList<>();
		for (String id : ids) {
			dtos.add(fromRedisGetItemById(id));
		}
		//2. 根据商家名称及对应的商品列表进行分组处理(有几个商家就有几个订单)
		Map<String, List<ItemDTO>> collect = dtos.stream().collect(Collectors.groupingBy(ItemDTO::getSeller));
		//3. 遍历collect集合
		collect.forEach((k,v) -> {
			//3.1 构造订单对象
			Order order = new Order();
			BigDecimal sum = new BigDecimal(0);   // 代表某个订单的总金额
			long id = idWorker.nextId();        // 生成订单id
			order.setSellerId(k);
			order.setUserId(getLogin());
			order.setCreateTime(LocalDateTime.now());
			order.setUpdateTime(LocalDateTime.now());
			order.setOrderId(id);               // 设置订单id
			order.setPaymentType(dto.getPaymentType());
			order.setStatus("1");               // 1: 未支付 2：己支付
			order.setReceiverAreaName(dto.getReceiverAreaName());
			order.setReceiver(dto.getReceiver());
			order.setReceiverMobile(dto.getReceiverMobile());
			order.setSourceType("2");           // 订单来源：1:app端，2：pc端，3：M端，4：微信端，5：手机qq端

			//3.2 生成订单项
			for (ItemDTO m : v) {
				long itemId = idWorker.nextId();
				OrderItem item = new OrderItem();
				item.setNum(m.getBuyNum());
				item.setItemId(m.getId());
				item.setPrice(m.getPrice());
				item.setSellerId(m.getSellerId());
				item.setGoodsId(m.getGoodsId());
				item.setPicPath(m.getImage());
				item.setId(itemId);
				item.setOrderId(id);        // 订单的id
				BigDecimal bd = m.getPrice().multiply(new BigDecimal(m.getBuyNum()));       // 计算小计
				item.setTotalFee(bd);
				// 累加某个订单的总金额
				sum = sum.add(bd);
				// 添加订单项到订单项列表中
				items.add(item);
			}
			totalMoney[0] = totalMoney[0].add(sum); // 累加所有订单的总金额
			order.setPayment(sum);                  // 设置订单的总金额
			orders.add(order);                      // 添加订单到订单列表中

		});

		return totalMoney[0];




	}

	// 从redis中根据商品id获取商品
	private ItemDTO fromRedisGetItemById(String id) {
		String str = (String) redisTemplate.opsForHash().get(HzitConstants.CART_PREFIX + getLogin(), id);
		return JSON.parseObject(str,ItemDTO.class);
	}
	// 获取登录名
	public String getLogin(){
		return LoginInterceptor.loginThreadLocal.get();
	}
}
